// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/speech/speech_recognizer_impl_android.h"

#include <stddef.h>

#include "base/android/context_utils.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/speech_recognition_event_listener.h"
#include "content/public/browser/speech_recognition_manager.h"
#include "content/public/browser/speech_recognition_session_config.h"
#include "content/public/common/speech_recognition_grammar.h"
#include "content/public/common/speech_recognition_result.h"
#include "jni/SpeechRecognition_jni.h"

using base::android::AppendJavaStringArrayToStringVector;
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::GetApplicationContext;
using base::android::JavaFloatArrayToFloatVector;
using base::android::JavaParamRef;

namespace content {

SpeechRecognizerImplAndroid::SpeechRecognizerImplAndroid(
    SpeechRecognitionEventListener* listener,
    int session_id)
    : SpeechRecognizer(listener, session_id)
    , state_(STATE_IDLE)
{
}

SpeechRecognizerImplAndroid::~SpeechRecognizerImplAndroid() { }

void SpeechRecognizerImplAndroid::StartRecognition(
    const std::string& device_id)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    // TODO(xians): Open the correct device for speech on Android.
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&SpeechRecognitionEventListener::OnRecognitionStart, base::Unretained(listener()), session_id()));
    SpeechRecognitionSessionConfig config = SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id());
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&content::SpeechRecognizerImplAndroid::StartRecognitionOnUIThread, this, config.language, config.continuous, config.interim_results));
}

void SpeechRecognizerImplAndroid::StartRecognitionOnUIThread(
    const std::string& language,
    bool continuous,
    bool interim_results)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    JNIEnv* env = AttachCurrentThread();
    j_recognition_.Reset(Java_SpeechRecognition_createSpeechRecognition(env,
        GetApplicationContext(), reinterpret_cast<intptr_t>(this)));
    Java_SpeechRecognition_startRecognition(
        env, j_recognition_, ConvertUTF8ToJavaString(env, language), continuous,
        interim_results);
}

void SpeechRecognizerImplAndroid::AbortRecognition()
{
    if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
        state_ = STATE_IDLE;
        BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&content::SpeechRecognizerImplAndroid::AbortRecognition, this));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    JNIEnv* env = AttachCurrentThread();
    if (!j_recognition_.is_null())
        Java_SpeechRecognition_abortRecognition(env, j_recognition_);
}

void SpeechRecognizerImplAndroid::StopAudioCapture()
{
    if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
        BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&content::SpeechRecognizerImplAndroid::StopAudioCapture, this));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    JNIEnv* env = AttachCurrentThread();
    if (!j_recognition_.is_null())
        Java_SpeechRecognition_stopRecognition(env, j_recognition_);
}

bool SpeechRecognizerImplAndroid::IsActive() const
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    return state_ != STATE_IDLE;
}

bool SpeechRecognizerImplAndroid::IsCapturingAudio() const
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    return state_ == STATE_CAPTURING_AUDIO;
}

void SpeechRecognizerImplAndroid::OnAudioStart(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(
            BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnAudioStart, this, nullptr,
                nullptr));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    state_ = STATE_CAPTURING_AUDIO;
    listener()->OnAudioStart(session_id());
}

void SpeechRecognizerImplAndroid::OnSoundStart(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(
            BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnSoundStart, this, nullptr,
                nullptr));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    listener()->OnSoundStart(session_id());
}

void SpeechRecognizerImplAndroid::OnSoundEnd(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnSoundEnd,
                this, nullptr, nullptr));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    listener()->OnSoundEnd(session_id());
}

void SpeechRecognizerImplAndroid::OnAudioEnd(JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnAudioEnd,
                this, nullptr, nullptr));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    if (state_ == STATE_CAPTURING_AUDIO)
        state_ = STATE_AWAITING_FINAL_RESULT;
    listener()->OnAudioEnd(session_id());
}

void SpeechRecognizerImplAndroid::OnRecognitionResults(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobjectArray>& strings,
    const JavaParamRef<jfloatArray>& floats,
    jboolean provisional)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    std::vector<base::string16> options;
    AppendJavaStringArrayToStringVector(env, strings, &options);
    std::vector<float> scores(options.size(), 0.0);
    if (floats != NULL)
        JavaFloatArrayToFloatVector(env, floats, &scores);
    SpeechRecognitionResults results;
    results.push_back(SpeechRecognitionResult());
    SpeechRecognitionResult& result = results.back();
    CHECK_EQ(options.size(), scores.size());
    for (size_t i = 0; i < options.size(); ++i) {
        result.hypotheses.push_back(SpeechRecognitionHypothesis(
            options[i], static_cast<double>(scores[i])));
    }
    result.is_provisional = provisional;
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread, this, results));
}

void SpeechRecognizerImplAndroid::OnRecognitionResultsOnIOThread(
    SpeechRecognitionResults const& results)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    listener()->OnRecognitionResults(session_id(), results);
}

void SpeechRecognizerImplAndroid::OnRecognitionError(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jint error)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(
            BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnRecognitionError, this,
                nullptr, nullptr, error));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    SpeechRecognitionErrorCode code = static_cast<SpeechRecognitionErrorCode>(error);
    listener()->OnRecognitionError(session_id(), SpeechRecognitionError(code));
}

void SpeechRecognizerImplAndroid::OnRecognitionEnd(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj)
{
    if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
        BrowserThread::PostTask(
            BrowserThread::IO, FROM_HERE,
            base::Bind(&SpeechRecognizerImplAndroid::OnRecognitionEnd, this,
                nullptr, nullptr));
        return;
    }
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    state_ = STATE_IDLE;
    listener()->OnRecognitionEnd(session_id());
}

// static
bool SpeechRecognizerImplAndroid::RegisterSpeechRecognizer(JNIEnv* env)
{
    return RegisterNativesImpl(env);
}

} // namespace content
